/*
 * $RCSfile: BackupAction.java,v $
 * $Revision: 1.1 $
 * $Date: 2013-12-15 $
 *
 * Copyright (C) 2008 Skin, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skin, Inc.
 * Use is subject to license terms.
 */
package com.skin.webcat.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.skin.j2ee.action.BaseAction;
import com.skin.j2ee.util.JsonUtil;
import com.skin.webcat.database.Table;
import com.skin.webcat.database.handler.TableHandler;
import com.skin.webcat.exchange.CsvDataExport;
import com.skin.webcat.exchange.DataExport;
import com.skin.webcat.exchange.DefaultDataExport;
import com.skin.webcat.exchange.SqlDataExport;
import com.skin.webcat.exchange.backup.BackupContext;
import com.skin.webcat.exchange.backup.Progress;
import com.skin.webcat.util.IO;
import com.skin.webcat.util.Jdbc;
import com.skin.webcat.util.Webcat;

/**
 * <p>Title: BackupAction</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class BackupAction extends BaseAction {
    private static final Logger logger = LoggerFactory.getLogger(DataImportAction.class);

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/index.html")
    public void index() throws ServletException, IOException {
        Connection connection = null;
        String name = this.getTrimString("name");
        String database = this.getTrimString("database");

        try {
            connection = Webcat.getConnection(name, database);
            TableHandler tableHandler = new TableHandler(connection);
            List<Table> tableList = tableHandler.getTableList(null, null, "%", new String[]{"TABLE"}, false);
            this.setAttribute("tableList", tableList);
        }
        catch(Exception e) {
            logger.error(e.getMessage(), e);
        }
        finally {
            Jdbc.close(connection);
        }
        this.forward("/template/webcat/exchange/tableList.jsp");
    }

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/list.html")
    public void list() throws ServletException, IOException {
        String path = this.getServletContext().getRealPath("/WEB-INF/backup");
        File dir = new File(path);
        List<Map<String, Object>> fileList = new ArrayList<Map<String, Object>>();

        if(dir.exists() && dir.isDirectory()) {
            File[] files = dir.listFiles();

            for(File file : files) {
                String name = file.getName();
                if(file.isFile() && name.endsWith(".zip")) {
                    Map<String, Object> item = this.getFileItem(file);
                    fileList.add(item);
                }
            }
            this.setAttribute("fileList", fileList);
        }
        this.forward("/template/webcat/exchange/backupList.jsp");
    }

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/execute.html")
    public void backup() throws ServletException, IOException {
        final String type = this.getTrimString("type");
        final String name = this.getTrimString("name");
        final String database = this.getTrimString("database");
        final String[] tableNameList = this.getParameterValues("tableName");
        final String token = UUID.randomUUID().toString();
        final File dir = new File(this.getRealPath("/WEB-INF/backup"));

        if(tableNameList == null || tableNameList.length < 1) {
            JsonUtil.error(this.request, this.response, 500, "系统错误，缺少参数！");
            return;
        }

        if(!dir.exists()) {
            dir.mkdirs();
        }

        /**
         * 先准备好响应数据
         * 新的线程可能会被挂起, 一直在等待执行机会
         * 这些处理结果应该存储在分布式缓存中会更好一些
         * 后台系统调用很少没有必要用缓存了
         */
        BackupContext.setProgress(token, new Progress());

        java.util.concurrent.Executors.newCachedThreadPool().execute(new Runnable() {
            /**
             * 备份数据可能需要较长时间
             * 在新的线程中执行备份操作
             */
            @Override
            public void run() {
                BackupAction.this.backup(name, database, type, tableNameList, dir, token);
            } 
        });
        JsonUtil.success(this.request, this.response, token);
    }

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/getProgress.html")
    public void getProgress() throws ServletException, IOException {
        String token = this.getTrimString("token");

        if(token.length() > 0) {
            Progress progress = BackupContext.getProgress(token);

            if(progress != null) {
                JsonUtil.success(this.request, this.response, progress);
                return;
            }
        }
        JsonUtil.error(this.request, this.response, 500, "系统错误，请稍后再试！");
    }

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/detail.html")
    public void detail() throws ServletException, IOException {
        String zip = this.getTrimString("zip");
        String path = this.getServletContext().getRealPath("/WEB-INF/backup");
        File zipFile = new File(path, zip);

        if(zipFile.exists() && zipFile.getName().endsWith(".zip")) {
            List<ZipEntry> entryList = getEntryList(zipFile);
            this.setAttribute("zip", zip);
            this.setAttribute("entryList", entryList);
            this.forward("/template/webcat/exchange/zipDetail.jsp");
        }
        else {
            this.error(404, "The requestURI not found !");
        }
    }

    /**
     * @throws ServletException
     * @throws IOException
     */
    @com.skin.j2ee.annotation.UrlPattern("/webcat/backup/download.html")
    public void download() throws ServletException, IOException {
        String zip = this.getTrimString("zip");
        String name = this.getTrimString("name");
        String path = this.getServletContext().getRealPath("/WEB-INF/backup");
        File zipFile = new File(path, zip);

        if(zipFile.exists() && zipFile.getName().endsWith(".zip")) {
            if(name.length() < 1) {
                this.download(zipFile);
            }
            else {
                this.download(zipFile, name);
            }
        }
        else {
            this.error(404, "The requestURI not found !");
        }
    }

    /**
     * @param file
     */
    private void download(File file) throws IOException {
        InputStream inputStream = null;
        OutputStream outputStream = this.response.getOutputStream();

        try {
            inputStream = new FileInputStream(file);
            this.response.setHeader("Cache-Control", "private");
            this.response.setHeader("Cache-Control", "no-cache");
            this.response.setDateHeader("Expires", System.currentTimeMillis());
            this.response.setContentType("application/zip");
            this.response.setContentLength((int)(file.length()));
            this.response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
            IO.copy(inputStream, outputStream, 4096);
            outputStream.flush();
        }
        finally{
            IO.close(inputStream);
        }
    }

    /**
     * @param file
     * @param name
     */
    private void download(File file, String name) throws IOException {
        ZipFile zipFile = null;
        ZipEntry zipEntry = null;
        InputStream inputStream = null;
        OutputStream outputStream = this.response.getOutputStream();

        try {
            zipFile = new ZipFile(file);
            zipEntry = zipFile.getEntry(name);

            if(zipEntry == null) {
                throw new IOException(name + " not exists !");
            }
            
            int contentLength = (int)(zipEntry.getSize());
            inputStream = zipFile.getInputStream(zipEntry);

            this.response.setHeader("Cache-Control", "private");
            this.response.setHeader("Cache-Control", "no-cache");
            this.response.setDateHeader("Expires", System.currentTimeMillis());
            this.response.setContentType("application/futuresplash");
            this.response.setContentLength(contentLength);
            this.response.setHeader("Content-Disposition", "attachment; filename=\"" + name + "\"");
            IO.copy(inputStream, outputStream, 4096);
            outputStream.flush();
        }
        finally {
            if(inputStream != null) {
                try {
                    inputStream.close();
                }
                catch(IOException e) {
                }
            }

            if(zipFile != null) {
                try {
                    zipFile.close();
                }
                catch(IOException e) {
                }
            }
        }
    }

    /**
     * @param type
     * @return DataExport
     */
    protected DataExport getDataExport(String type) {
        if(type == null) {
            return new SqlDataExport();
        }
        else if(type.equals("txt")) {
            return new DefaultDataExport();
        }
        else if(type.equals("csv")) {
            return new CsvDataExport();
        }
        else {
            return new SqlDataExport();
        }
    }

    /**
     * @param dataExport
     * @param tableNameList
     */
    protected void backup(String name, String database, String type, String[] tableNameList, File dir, String token) {
        Connection connection = null;
        OutputStream outputStream = null;
        ZipOutputStream zipOutputStream = null;

        int i = 0;
        boolean success = false;
        float total = tableNameList.length;
        File file = new File(dir, token + ".temp");
        DataExport dataExport = this.getDataExport(type);

        try  {
            connection = Webcat.getConnection(name, database);
            dataExport.setConnection(connection);
            outputStream = new FileOutputStream(file);
            zipOutputStream = new ZipOutputStream(outputStream);

            for(String table : tableNameList) {
                ZipEntry entry = new ZipEntry(table + ".sql");
                zipOutputStream.putNextEntry(entry);

                Progress progress = BackupContext.getProgress(token);
                progress.setProgress((int)(i / total * 100));
                progress.setTable(table);

                dataExport.execute(table, zipOutputStream, "utf-8");
                i++;
            }

            zipOutputStream.finish();
            zipOutputStream.flush();
            outputStream.flush();
            success = true;
        }
        catch(Exception e) {
            logger.error(e.getMessage(), e);
        }
        finally {
            IO.close(zipOutputStream);
            IO.close(outputStream);
            Jdbc.close(connection);
        }

        if(success) {
            logger.info("backup complete !");

            try {
                File target = this.getTarget(dir, ".zip");
                file.renameTo(target);

                Progress progress = BackupContext.getProgress(token);
                progress.setProgress((int)(i / total * 100));
                progress.setFile(target.getName());
            }
            catch(IOException e) {
                logger.error(e.getMessage(), e);
            }
        }
        else {
            logger.info("backup failed !");
            BackupContext.remove(token);
            file.deleteOnExit();
        }

        /**
         * 30秒之后删除
         */
        try {
            Thread.sleep(30L * 1000L);
            BackupContext.remove(token);
        }
        catch (InterruptedException e) {
        }
    }

    /**
     * @param file
     * @return List<Map<String, Object>>
     */
    protected List<ZipEntry> getEntryList(File file) throws IOException {
        ZipFile zipFile = null;
        ZipInputStream zipInputStream = null;
        ZipEntry zipEntry = null;
        List<ZipEntry> entryList = new ArrayList<ZipEntry>();

        try {
            zipFile = new ZipFile(file);
            zipInputStream = new ZipInputStream(new FileInputStream(file));

            while((zipEntry = zipInputStream.getNextEntry()) != null) {
                if(zipEntry.isDirectory()) {
                    continue;
                }

                /**
                 * zipFile.getEntry获取的zipEntry才包括全部属性
                 */
                zipEntry = zipFile.getEntry(zipEntry.getName());
                ZipEntry entry = new ZipEntry(zipEntry.getName());
                entry.setSize(zipEntry.getSize());
                entry.setCompressedSize(zipEntry.getCompressedSize());
                entry.setTime(zipEntry.getTime());
                entry.setComment(zipEntry.getComment());
                entryList.add(entry);
            }
        }
        finally {
            if(zipFile != null) {
                try {
                    zipFile.close();
                }
                catch(IOException e) {
                }
            }

            if(zipInputStream != null) {
                try {
                    zipInputStream.close();
                }
                catch(IOException e) {
                }
            }
        }
        return entryList;
    }

    /**
     * @param target
     * @param fileList
     */
    public void delete(File target, List<File> fileList) {
        for(File file : fileList) {
            try {
                file.delete();
            }
            catch(Exception e) {
            }
        }

        try {
            target.delete();
        }
        catch(Exception e) {
        }
    }

    /**
     * @param parent
     * @param extension
     * @return File
     * @throws IOException
     */
    private File getTarget(File parent, String extension) throws IOException {
        File target = null;
        long timeMillis = System.currentTimeMillis();
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");

        while(true) {
            timeMillis += 1000;
            Date currentTime = new Date(timeMillis);
            target = new File(parent, dateFormat.format(currentTime) + extension);

            if(target.exists() == false) {
                break;
            }
        }
        return target;
    }

    /**
     * @param file
     * @return Map<String, Object>
     */
    private Map<String, Object> getFileItem(File file) {
        long length = file.length();
        Map<String, Object> item = new HashMap<String, Object>();
        item.put("name", file.getName());
        item.put("path", file.getPath());
        item.put("length", file.length());
        item.put("bytes", (length / 1024 / 1024) + "M (" + length + ")");
        return item;
    }
}
